摘要
Day 20 已經把合作方政策從 Java enum 拆成可載入的 contract JSON。Day 21 修正 contract JSON 使其更貼近真實合作方交換契約;不能在 MVP 內合理完成的部分,明確列成 production gap。
FHIR / TW Core validator 可以回答:
這份 Resource 或 Bundle 是否符合 FHIR 結構、Profile、binding 與 IG?
但真實交換前還會遇到另一個問題:
這份資料在指定合作方、指定版本、指定交換情境下,可不可以送出?
所以 Day 21 之後,本專案的定位更精準:
FHIR/TW Core validator = Profile validation
TW Lab Contract Gate = Profile validation + partner exchange contract gate
它概念上類似 validator.dicom.tw/app/ 這種 FHIR validation 入口,但多了一層可載入的合作方交換契約。
也就是說,它不是取代 validator,而是在 validator 結果之外再加上:
合作方 policy assertions
契約 lifecycle
SHALL / SHOULD / MAY blocking policy
LOINC / UCUM terminology policy snapshot
版本比較與 upgrade blocker evidence
今天新增或修改的範圍有:
src/main/java/com/twlab/qualitygate/validation/ExchangeContract.java
src/main/java/com/twlab/qualitygate/validation/ExchangeContractService.java
src/main/java/com/twlab/qualitygate/validation/BundleParseService.java
src/main/resources/contracts/demo-lab-v1.0.json
src/main/resources/contracts/demo-lab-v1.1.json
src/main/resources/contracts/demo-lab-hospital-a-v1.2-reference.json
src/main/resources/templates/index.html
src/test/java/com/twlab/qualitygate/validation/BundleParseServiceTests.java
src/test/java/com/twlab/qualitygate/validation/ExchangeContractServiceTests.java
src/test/java/com/twlab/qualitygate/validation/TestExchangeContracts.java
src/test/java/com/twlab/qualitygate/web/ParseControllerTests.java
docs/references/fhir-validation-and-exchange-contracts.md
README.md
Day 21 做六件事:
把外部 contract JSON 從 rule-code list 改成 policyAssertions。
替 contract 補 lifecycle metadata。
替 policy assertion 補 obligation / severity。
讓 SHALL error/fatal failure 才 block,SHOULD/MAY failure 只進 warning。
替 LOINC / UCUM 補 terminology policy metadata。
把仍不符合 production FHIR exchange 的部分明確列成 gap。
HL7 FHIR validation 官方文件本來就把 validation 和 business rule 分開看。
FHIR validation 可以檢查:
其中 business rules 是規格之外制定的規則,例如 duplicate check、reference resolution、authorization 等。
所以本專案的 exchange contract layer 不是亂加一層,而是把「標準之外的合作方政策」變成可驗證證據。
但也要講清楚:
本專案的 contract JSON 不是 FHIR 官方 resource。
它不是 ImplementationGuide package。
它不是 CapabilityStatement。
它也不是 terminology server。
它目前比較接近:
companion guide / interface specification / trading partner agreement
在 MVP 裡的可執行投影。
截至 2026-08-22,單筆 Resource / Profile validation 最應該引用的是:
HL7 FHIR Validator
https://validator.fhir.org/
Inferno 仍然重要,但定位不同。
Inferno 更適合 FHIR Server / Client conformance testing、API interaction、test kit。
而 Inferno Resource Validator 在 2026-07-13 的公告中提到,individual FHIR Resource validator service 預計最快 2026-08 停用,並建議使用 HL7 validator.fhir.org 這類替代服務。
所以本專案和現有工具的區別為:
| 工具 | 回答的問題 |
|---|---|
HL7 FHIR Validator / $validate |
Resource 是否符合 FHIR / Profile / IG |
| TW Core official validation guide | 如何用 tw.gov.mohw.twcore#1.0.0 驗證 TW Core |
| Inferno / Touchstone | FHIR API / Server / Client 行為是否符合測試情境 |
| TW Lab Contract Gate | 同一份 Bundle 在指定合作方契約下能不能送出 |
Day 20 的 contract 還比較像:
這些 rule code 要不要跑?
允許哪些 LOINC / UCUM?
Day 21 改成比較接近 companion guide 條文:
{
"id": "demo-lab-hospital-a",
"name": "Demo Lab to Hospital A Exchange Contract",
"version": "1.1",
"status": "active",
"publisher": "Demo Lab Integration Office",
"jurisdiction": "TW",
"effectiveDate": "2026-08-21",
"retireDate": null,
"fhirVersion": "4.0.1",
"policyAssertions": [
{
"id": "LAB-UNIT-002",
"source": "demo-lab-hospital-a companion guide section 3.3",
"requirement": "Observation.valueQuantity.system SHALL be UCUM and code SHALL be allowed by this exchange scenario.",
"obligation": "SHALL",
"severity": "error",
"enabled": true
}
],
"terminologyPolicy": {
"loincSystem": "http://loinc.org",
"loincVersion": "2.78",
"loincValueSetCanonical": "https://example.org/fhir/ValueSet/demo-lab-hospital-a-lab-codes|1.1",
"ucumSystem": "http://unitsofmeasure.org",
"ucumVersion": "2.1",
"ucumValueSetCanonical": "https://example.org/fhir/ValueSet/demo-lab-hospital-a-lab-units|1.1",
"expansionTimestamp": "2026-08-21T00:00:00+08:00",
"scope": "Demo lab exchange subset; runtime uses allowed code arrays as a local expansion snapshot."
},
"allowedLoincCodes": ["2345-7", "718-7"],
"allowedUcumCodes": ["mg/dL", "mmol/L"]
}
這個格式仍然不是 FHIR-native。
但它比單純 rule-code list 更接近真實交換契約,因為它保留了:
SHALL。SHOULD 或 MAY。今天把 contract 與 rule implementation 的分工重新收斂。
Java rule class 仍然負責:
Observation、DiagnosticReport、Patient。Observation.code.coding 是否含有允許的 LOINC。Observation.valueQuantity.system/code 是否符合 UCUM policy。RuleResult。Contract file 則負責:
policyAssertions 在這個版本啟用。SHALL、SHOULD 還是 MAY。這樣 LAB-UNIT-002 的意思變成:
| 層次 | 負責內容 |
|---|---|
| Java rule | 會檢查 Observation.valueQuantity.system/code |
| policy assertion | 說這條要求來自哪個合作方條文、是否啟用、是否阻擋 |
| terminology policy | 說這次允許值是哪個 ValueSet snapshot 的本機投影 |
Day 20 只要 rule fail,就會 block。
這對 demo 很直覺,但不夠像真實 companion guide。
Day 21 改成:
| obligation | severity | Rule failed 時的 Gate |
|---|---|---|
SHALL |
error / fatal |
BLOCKED |
SHOULD |
warning |
PASS_WITH_WARNINGS |
MAY |
information / warning |
PASS_WITH_WARNINGS 或不阻擋 |
也就是說,現在 failure 不只看 rule 本身,也看契約條文的強度。
新增測試確認:
同一份 UCUM 錯誤資料
如果 contract 把 LAB-UNIT-002 定義成 SHALL/error -> BLOCKED
如果 contract 把 LAB-UNIT-002 定義成 SHOULD/warning -> PASS_WITH_WARNINGS
這比「所有規則失敗都阻擋」更接近真實交換契約。
真實情境通常不會只寫:
"allowedUcumCodes": ["mg/dL", "mmol/L"]
比較合理的是引用 ValueSet canonical、版本與 expansion。
所以 Day 21 補上:
"terminologyPolicy": {
"loincSystem": "http://loinc.org",
"loincVersion": "2.78",
"loincValueSetCanonical": "https://example.org/fhir/ValueSet/demo-lab-hospital-a-lab-codes|1.1",
"ucumSystem": "http://unitsofmeasure.org",
"ucumVersion": "2.1",
"ucumValueSetCanonical": "https://example.org/fhir/ValueSet/demo-lab-hospital-a-lab-units|1.1",
"expansionTimestamp": "2026-08-21T00:00:00+08:00"
}
但 runtime 仍然使用:
allowedLoincCodes
allowedUcumCodes
原因是目前 MVP 沒有 terminology server。
所以這裡必須誠實稱為:
local expansion snapshot
而不是正式 terminology validation。
Day 20 首頁只顯示:
Default validation: demo-lab-hospital-a#1.1
Current validation: demo-lab-hospital-a#1.1
Day 21 補上:
Contract lifecycle: active from 2026-08-21
Publisher: Demo Lab Integration Office
FHIR version: 4.0.1
Terminology snapshot: 2026-08-21T00:00:00+08:00
這讓使用者知道本次 Quality Gate 不是只套一個抽象版本號,而是套用一份有狀態、生效日與 terminology snapshot 的 contract。
已落地:
| 問題 | Day 21 處理方式 |
|---|---|
| 自定 rule-code list 不像真實契約 | 改成 policyAssertions |
| 沒有 lifecycle | 加 status、publisher、jurisdiction、effectiveDate、retireDate、fhirVersion |
| 沒有 SHALL / SHOULD / MAY | 加 obligation,並影響 Gate |
| severity 不可由契約控制 | 加 severity,rule result 會套用 contract severity |
| terminology 太 flat | 加 terminologyPolicy 與 local expansion snapshot 說明 |
未實作:
| Production gap | 為什麼不放進今天 MVP |
|---|---|
| 正式 FHIR IG package | 需要 IG publisher、profiles、ValueSet、examples 與 package build pipeline |
| Full terminology server validation | 需要 terminology server、ValueSet expansion、code system version、inactive code policy |
| CapabilityStatement validation | 需要 live FHIR endpoint,不是單份 Bundle JSON |
| External FHIR Server reference lookup | 需要 endpoint、auth、timeout、retry、錯誤分類與 PHI 傳輸控管 |
| OAuth / SMART / mTLS | 這是正式交換平台 security layer |
| Consent / Provenance / AuditEvent / PHI handling | 牽涉隱私、稽核、保存與權限設計 |
| Persistent history | 需要資料庫、retention policy、刪除策略與 access control |
| 完整 lab domain coverage | 需要明確合作方規格,例如 specimen、performer、organization、referenceRange、interpretation |
本機 Maven 測試:
./mvnw test
結果:
Tests run: 66, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS
Day 21 新增或調整的測試確認:
policyAssertions 可以控制 rule 是否啟用。policyAssertions 可以判斷 blocking policy。terminologyPolicy metadata 可以從 contract 載入。SHALL/error 的 UCUM failure 會 BLOCKED。SHOULD/warning 的 UCUM failure 會 PASS_WITH_WARNINGS。ExchangeContract 新增 lifecycle 欄位。ExchangeContract 新增 nested PolicyAssertion。ExchangeContract 新增 nested TerminologyPolicy。ExchangeContractService 加強 contract validation。BundleParseService 改成用 contract obligation / severity 判斷 Gate。demo-lab-v1.0.json 改成 policy assertion format。demo-lab-v1.1.json 改成 policy assertion format。demo-lab-hospital-a-v1.2-reference.json 作為 reference-only specimen。docs/references/fhir-validation-and-exchange-contracts.md。./mvnw test 通過,測試數 66。Day 21 尚未處理:
$expand / $validate-code。目前的 MVP 進度:
validation-flow
├─ JSON parse 完成
├─ FHIR R4 parse 完成
├─ FHIR R4 validation 完成
├─ TW Core validation / safe NOT_EVALUATED 完成
├─ Partner exchange contract loading
│ ├─ demo-lab-v1.0.json 完成
│ ├─ demo-lab-v1.1.json 完成
│ ├─ policyAssertions 完成
│ ├─ obligation / severity 完成
│ ├─ lifecycle metadata 完成
│ ├─ terminologyPolicy metadata 完成
│ ├─ allowedLoincCodes local snapshot 完成
│ ├─ allowedUcumCodes local snapshot 完成
│ └─ optional uploaded contract 完成最小版
├─ Exchange contract rules
│ ├─ LAB-REF-001 完成並由 contract 啟用
│ ├─ LAB-REF-002 完成並由 contract 啟用
│ ├─ LAB-REF-003 完成並由 contract 啟用
│ ├─ LAB-CODE-001 完成,允許值由 contract 提供
│ ├─ LAB-UNIT-001 完成並由 contract 啟用
│ └─ LAB-UNIT-002 完成,允許值由 contract 提供
├─ Quality Gate
│ ├─ SHALL error/fatal blocking 完成
│ └─ SHOULD/MAY warning behavior 完成
├─ Contract comparison
│ ├─ comparison service test 完成
│ ├─ homepage comparison display 完成
│ ├─ compare checkbox 完成
│ ├─ uploaded 2+ version comparison 完成
│ └─ upgrade blocker evidence display 完成
├─ Scenario test pack
│ ├─ v1.0 / v1.1 representative cases 完成 4 例
│ └─ SHOULD warning behavior 完成 1 例
├─ Homepage Quality Test Report 完成最小版
└─ Reproducible delivery
├─ Dockerfile 完成最小版
├─ Docker Compose 完成最小版並驗證啟動
└─ GitHub Actions CI 完成最小版
Repository:twcore-data-quality-gate